﻿using System.Text;

namespace PmSoft.Core.Cryptos;

/// <summary>
/// 提供用于编码和解码 URL 安全的 Base64 字符串的方法。
/// 在标准 Base64 和 URL 安全 Base64（使用 - 和 _ 替代 + 和 /）之间进行转换。
/// </summary>
public static class Base64UrlUtility
{
	// 字符替换的常量
	private const char PaddingChar = '=';          // 填充字符
	private const char StandardChar62 = '+';       // 标准 Base64 的第62个字符
	private const char StandardChar63 = '/';       // 标准 Base64 的第63个字符
	private const char UrlSafeChar62 = '-';        // URL 安全的第62个字符
	private const char UrlSafeChar63 = '_';        // URL 安全的第63个字符
	private const string DoublePadding = "==";     // 双倍填充字符串

	/// <summary>
	/// 将字符串编码为 URL 安全的 Base64 格式，使用 UTF-8 编码。
	/// </summary>
	/// <param name="input">要编码的字符串</param>
	/// <returns>URL 安全的 Base64 编码字符串</returns>
	/// <exception cref="ArgumentNullException">当输入为 null 时抛出</exception>
	public static string EncodeFromString(string input)
	{
		ArgumentNullException.ThrowIfNull(input);
		return EncodeFromBytes(Encoding.UTF8.GetBytes(input));
	}

	/// <summary>
	/// 将字节数组的指定段编码为 URL 安全的 Base64 格式。
	/// </summary>
	/// <param name="bytes">要编码的字节数组</param>
	/// <param name="offset">数组中的起始位置</param>
	/// <param name="length">要编码的字节数</param>
	/// <returns>URL 安全的 Base64 编码字符串</returns>
	/// <exception cref="ArgumentNullException">当字节数组为 null 时抛出</exception>
	public static string EncodeFromBytes(byte[] bytes, int offset, int length)
	{
		ArgumentNullException.ThrowIfNull(bytes);

		string base64 = Convert.ToBase64String(bytes, offset, length);
		return ToUrlSafeBase64(base64);
	}

	/// <summary>
	/// 将整个字节数组编码为 URL 安全的 Base64 格式。
	/// </summary>
	/// <param name="bytes">要编码的字节数组</param>
	/// <returns>URL 安全的 Base64 编码字符串</returns>
	/// <exception cref="ArgumentNullException">当字节数组为 null 时抛出</exception>
	public static string EncodeFromBytes(byte[] bytes)
	{
		ArgumentNullException.ThrowIfNull(bytes);

		string base64 = Convert.ToBase64String(bytes);
		return ToUrlSafeBase64(base64);
	}

	/// <summary>
	/// 将 URL 安全的 Base64 字符串解码为原始字节数组。
	/// </summary>
	/// <param name="encoded">要解码的 URL 安全 Base64 字符串</param>
	/// <returns>解码后的字节数组</returns>
	/// <exception cref="ArgumentNullException">当编码字符串为 null 时抛出</exception>
	/// <exception cref="FormatException">当输入格式无效时抛出</exception>
	public static byte[] DecodeToBytes(string encoded)
	{
		ArgumentNullException.ThrowIfNull(encoded);

		// 将 URL 安全字符转换回标准 Base64 字符
		string standardBase64 = encoded
			.Replace(UrlSafeChar62, StandardChar62)
			.Replace(UrlSafeChar63, StandardChar63);

		// 根据需要添加填充字符
		standardBase64 = AddPadding(standardBase64);

		return Convert.FromBase64String(standardBase64);
	}

	/// <summary>
	/// 将 URL 安全的 Base64 字符串解码为原始 UTF-8 字符串。
	/// </summary>
	/// <param name="encoded">要解码的 URL 安全 Base64 字符串</param>
	/// <returns>解码后的字符串</returns>
	/// <exception cref="ArgumentNullException">当编码字符串为 null 时抛出</exception>
	public static string DecodeToString(string encoded)
	{
		byte[] bytes = DecodeToBytes(encoded);
		return Encoding.UTF8.GetString(bytes);
	}

	/// <summary>
	/// 将标准 Base64 转换为 URL 安全格式，去除填充并替换字符。
	/// </summary>
	/// <param name="base64">标准 Base64 字符串</param>
	/// <returns>URL 安全的 Base64 字符串</returns>
	private static string ToUrlSafeBase64(string base64)
	{
		int paddingIndex = base64.IndexOf(PaddingChar);
		if (paddingIndex != -1)
		{
			base64 = base64.Substring(0, paddingIndex);
		}

		return base64
			.Replace(StandardChar62, UrlSafeChar62)
			.Replace(StandardChar63, UrlSafeChar63);
	}

	/// <summary>
	/// 添加必要的填充字符，使字符串长度为 4 的倍数。
	/// </summary>
	/// <param name="input">需要填充的字符串</param>
	/// <returns>添加填充后的字符串</returns>
	/// <exception cref="FormatException">当输入长度模 4 不合法时抛出</exception>
	private static string AddPadding(string input)
	{
		return (input.Length % 4) switch
		{
			0 => input,
			2 => input + DoublePadding,
			3 => input + PaddingChar,
			_ => throw new FormatException($"无效的 Base64 字符串长度: {input}")
		};
	}
}